import { isFunction, isObject, kebabCase, lowerFirst, forEach, isArray } from 'lodash-es'
import { throttle, debounce } from '../utils'

import button from './modules/button'
import cascader from './modules/cascader'
import checkbox from './modules/checkbox'
import datePicker from './modules/datePicker'
import formItem from './modules/formItem'
import input from './modules/input'
import inputNumber from './modules/inputNumber'
import link from './modules/link'
import radio from './modules/radio'
import select from './modules/select'
import slider from './modules/slider'
import string from './modules/string'
import _switch from './modules/switch'
import tableColumn from './modules/tableColumn'
import timeSelect from './modules/timeSelect'
import tooltip from './modules/tooltip'
import transfer from './modules/transfer'

const renderMap = {
  button,
  cascader,
  checkbox,
  datePicker,
  formItem,
  input,
  inputNumber,
  link,
  radio,
  select,
  slider,
  string,
  switch: _switch,
  tableColumn,
  timeSelect,
  tooltip,
  transfer
}

// const cx = require.context('./modules', false, /\.js$/)
// const renderMap = {}
// for (const iterator of cx.keys()) {
//   const name = /(\w*).js$/.exec(iterator)?.[1]
//   const module = cx(iterator).default
//   renderMap[name] = module
// }

export default {
  name: 'RenderHelpers',
  functional: true,
  props: ['tag', 'hidden', 'hiddenClear'],
  render(h, ctx) {
    const { data, props, children, parent } = ctx
    const { model, prop } = data.attrs
    const { tag, hidden, hiddenClear } = props
    if (hidden) {
      hiddenClear && model && prop && parent.$delete(model, prop)
      return
    }
    if (isFunction(tag)) {
      return tag(h, ctx)
    } else if (isObject(tag)) {
      return <tag {...data}>children</tag>
    }
    const render = renderMap[tag] || tag
    if (!render) return
    return <render {...data}>{children}</render>
  }
}

/**
 * @description: 获取插槽，名字使用下划线分隔
 * @param {VueInstance} vm
 * @param {string[]} names
 * @return {vNode}
 */
export function getSlot(vm, names = [], params) {
  const { $slots, $scopedSlots, $createElement: h } = vm
  if (isFunction(names)) return scoped => names(h, Object.assign({}, params, scoped))
  const name = isArray(names) ? names.join('_') : names
  if ($scopedSlots[name]) return scoped => $scopedSlots[name](Object.assign({}, params, scoped))
  if ($slots[name]) return () => $slots[name]
}

const rootAttributes = ['class', 'style', 'key', 'ref', 'refInFor', 'slot', 'scopedSlots']
const prefixes = ['props', 'domProps', 'attrs']
const prefixesEvent = ['on', 'nativeOn']
const isDirective = src => src.startsWith('v') && src.length >= 2 && src[1] >= 'A' && src[1] <= 'Z'
const prefixEvent = src => prefixesEvent.find(prefix => src.startsWith(prefix))

/**
 * @description: 解析配置项
 * @param {object} source - 源配置
 * @param {object} options - 配置项
 * @return {object}
 */
export function parseAttribute(source, options = {}, target = {}) {
  const { eventParams, context, extraParams } = options
  const params = []
  eventParams && params.push(eventParams)
  context && params.push(context)
  extraParams && params.push(extraParams)
  const { update = Function.prototype, ...rest } = source
  let prefix, directives, events, obj

  for (let [key, value] of Object.entries(Object.assign(rest, update(...params)))) {
    if (rootAttributes.includes(key)) {
      // 顶层选项处理
      target[key] = value
    } else if (isDirective(key)) {
      /* 指令处理 */
      directives = target.directives || (target.directives = [])
      key = lowerFirst(key.slice(1))
      directives.push({ name: key, value })
    } else if ((prefix = prefixEvent(key))) {
      /* 事件处理 */
      events = target[prefix] || (target[prefix] = {})
      key = kebabCase(key.replace(new RegExp(`^${prefix}`), ''))
      events[key] = resolveEvent(value, { ...source, ...options, params })
    } else {
      /* 其它特殊选项处理，attrs 兜底处理 */
      prefix = prefixes.find(prefix => key.startsWith(prefix)) || 'attrs'
      obj = target[prefix] || (target[prefix] = {})
      key = lowerFirst(key.replace(new RegExp(`^${prefix}`), ''))
      obj[key] = value
    }
  }

  return target
}

/**
 * @description: 获取事件
 * @param {function} event - 源事件
 * @param {object} options - 配置项
 * @param {any} options.eventParams - 事件第一个参数
 * @param {any} options.context - 上下文，一般是组件实例，事件第二个参数
 * @param {any} options.extraParams - 扩展参数
 * @return {function}
 */
function resolveEvent(event, options) {
  isFunction(event) && (event = { handler: event })
  const { context, params } = options
  const { throttle: _throttle, debounce: _debounce, __isThrottle__, __isDebounce__ } = event
  if (_throttle && !__isThrottle__) {
    event.handler = throttle(event.handler, _throttle, context)
    event.__isThrottle__ = true
  } else if (_debounce && !__isDebounce__) {
    event.handler = debounce(event.handler, _debounce, context)
    event.__isDebounce__ = true
  }
  return event.handler.bind(context, ...params)
}

/**
 * @description: 生命周期
 * @param {*} source 源配置项
 */
export function lifeCycle(source, ...rest) {
  forEach(source, (item, index, target) => {
    const { created, computed, watch } = item
    created && created.call(this, [item, index, target], ...rest)
    computed &&
      watch &&
      this.$watch(computed.bind(this, [item, index, target], ...rest), watch.bind(this, [item, index, target], ...rest))
  })
}
